import time
from datetime import datetime

import matplotlib.pyplot as plt
import mplfinance as mpf
import numpy as np
import pandas as pd
import pytz
import schedule
from BarkNotificator import BarkNotificator
from binance.spot import Spot
from binance.um_futures import UMFutures
from mplfinance.original_flavor import candlestick_ohlc


def format_time(timestamp):
    tz = pytz.timezone("Asia/Shanghai")
    return datetime.fromtimestamp(timestamp, tz).strftime("%Y-%m-%d %H:%M:%S")


def convert_to_beijing_time(ts):
    tz = pytz.timezone("Asia/Shanghai")
    return datetime.fromtimestamp(ts // 1e3, tz)


btc = "BTCUSDC"
bnb = "BNBUSDC"
# symbol = "ETHUSDT"

# key = "FC9r3f44u9UgPqnG5wzmW1FlTdUrzu8Bu1G3R18kyv1HdtNo7FedLgsuS3oo6bLH"
# secret = "fjRHyLILCVw0uj29mFTribS94xeFw35SJXjYpkoCsEybPSFeOdrqyBgdzKZkGSBc"

# region limited
# 连续合约K线数据


def get_futures_continous_data(symbol="BTCUSDT", interval="15m", limit="4"):
    um_futures_client = UMFutures()
    klines = um_futures_client.klines(symbol, interval, limit=limit)

    # 2. 转换为结构化数据
    df = pd.DataFrame(
        klines[:-1],#不含当前未闭合k线
        columns=[
            "Open Time",
            "open",
            "high",
            "low",
            "close",
            "Volume",
            "Close Time",
            "Quote Asset Volume",
            "Number of Trades",
            "Taker Buy Base",
            "Taker Buy Quote",
            "Ignore",
        ],
    )

    df["Time"] = df["Open Time"].apply(convert_to_beijing_time)

    # # 4. 构建OHLC数据框架
    ohlc = df.copy()
    ohlc = ohlc.set_index("Time").astype(float)  # 确保数值类型正确

    return ohlc


def get_data(symbol="BTCUSDT", interval="15m", limit="4"):
    # 1. 获取K线原始数据
    # client = Spot(base_url="https://data-api.binance.vision")
    client = Spot(base_url="https://data-api.binance.vision")
    # interval 1s, 1m, 5m, 1h, 1d,
    # 1125-1216 1732475182000-1734289582000 震荡
    # 1106-1125 1730833582000-1732475182000 上涨
    # 0124 1737659180000
    klines = client.klines(
        symbol=symbol,
        interval=interval,
        limit=limit,
        # startTime="1741276800000",  # 2025-03-07 00:00:00+08:00
        # startTime="1730833582000",
        # startTime="1737659180000",
        # startTime="1730833582000",
        # startTime="1732475182000",
        # endTime="1734289582000",
    )  # 示例：BTC/USDT 1小时K线

    # 2. 转换为结构化数据
    df = pd.DataFrame(
        klines,
        columns=[
            "Open Time",
            "open",
            "high",
            "low",
            "close",
            "Volume",
            "Close Time",
            "Quote Asset Volume",
            "Number of Trades",
            "Taker Buy Base",
            "Taker Buy Quote",
            "Ignore",
        ],
    )

    df["Time"] = df["Open Time"].apply(convert_to_beijing_time)

    # # 4. 构建OHLC数据框架
    ohlc = df.copy()
    ohlc = ohlc.set_index("Time").astype(float)  # 确保数值类型正确
    # print(ohlc)
    return ohlc


class FractalOrderBlockStrategy:
    def __init__(self, data, fractal_period=5, fvg_distance=3):
        self.data = data  # OHLC数据
        self.fractal_period = fractal_period  # 分形检测周期 5
        self.fvg_distance = fvg_distance  # FVG最大间隔 3
        self.data['mss'] = None

    def detect_fractals(self):
        """分形检测逻辑"""
        # 通过滚动窗口检测高低分形
        self.data['fractal_highs'] = self.data['high'].rolling(self.fractal_period,  center=True).apply(
            lambda x: x.iloc[2] if x.iloc[2] > max(x.iloc[:2]) and x.iloc[2] > max(x.iloc[3:]) else np.nan)
        self.data['fractal_lows'] = self.data['low'].rolling(self.fractal_period,  center=True).apply(
            lambda x: x.iloc[2] if x.iloc[2] < min(x.iloc[:2]) and x.iloc[2] < min(x.iloc[3:]) else np.nan)
        return self.data

    def identify_fvg(self):
        """FVG缺口检测"""
        # 检测bullishImb/bearishImb条件
        self.data['bull_fvg'] = (self.data['close'].shift(1) > self.data['high'].shift(2)) & (self.data['low'] > self.data['high'].shift(2))
        self.data['bear_fvg'] = (self.data['close'].shift(1) < self.data['low'].shift(2)) & (self.data['high'] < self.data['low'].shift(2))
        return self.data

    def find_orderblocks_old(self):
        """订单块生成逻辑"""
        # 结合分形突破与FVG过滤
        self.data['ob_bull'] = np.where(
            (self.data['close'] > self.data['fractal_highs'].ffill()) &
            (self.data['bull_fvg'].rolling(self.fvg_distance).max()),
            True, False)

        self.data['ob_bear'] = np.where(
            (self.data['close'] < self.data['fractal_lows'].ffill()) &
            (self.data['bear_fvg'].rolling(self.fvg_distance).max()),
            True, False)

        self.data['ob'] = np.where(
            (self.data['close'] > self.data['fractal_highs'].ffill()) &  # 1,
            (self.data['bull_fvg'].rolling(self.fvg_distance).max()),
            1, np.where(
                (self.data['close'] < self.data['fractal_lows'].ffill()) &  # 1,
                (self.data['bear_fvg'].rolling(self.fvg_distance).max()),
                -1, np.nan))
        self.data['ob'] = self.data['ob'].ffill()
        self.data['mss'] = self.data['ob'].diff()
        self.data['mss'] = self.data['mss'].fillna(value=0)
        
        return self.data

    def find_orderblocks(self):
        """
        订单块（Order Block）生成逻辑
        
        处理步骤：
        1. 处理分形高点和低点，获取有效的参考价格
        2. 计算公平价值缺口（FVG）的滚动最大值
        3. 识别看多和看空订单块
        4. 生成综合订单块信号
        
        Returns:
            pd.DataFrame: 包含订单块信号的数据框
        """
        # 1. 处理分形数据，获取有效的参考价格
        # - 对fractal_highs使用ffill()填充NaN，然后shift(1)获取上一个高点
        # - 这确保我们比较的是上一个有效的分形高点，而不是当前高点
        last_valid_fractal_highs = self.data['fractal_highs'].ffill().shift(1)
        # - 对fractal_lows只需填充NaN即可，用于当前价格比较
        last_valid_fractal_lows = self.data['fractal_lows'].ffill()

        # 2. 计算公平价值缺口（FVG）的滚动窗口最大值
        # - 使用self.fvg_distance作为窗口大小
        # - rolling().max()计算窗口内是否出现过FVG
        bull_fvg_max = self.data['bull_fvg'].rolling(self.fvg_distance).max()
        bear_fvg_max = self.data['bear_fvg'].rolling(self.fvg_distance).max()

        # 3. 识别看多和看空订单块
        # 看多订单块条件：
        # - 收盘价大于上一个分形高点
        # - 且在指定窗口内出现过看多FVG
        self.data['ob_bull'] = (self.data['close'] > last_valid_fractal_highs) & bull_fvg_max
        
        # 看空订单块条件：
        # - 收盘价小于当前分形低点
        # - 且在指定窗口内出现过看空FVG
        self.data['ob_bear'] = (self.data['close'] < last_valid_fractal_lows) & bear_fvg_max

        # 4. 生成综合订单块信号
        # 使用np.select进行向量化选择：
        # - 满足看多条件时返回1
        # - 满足看空条件时返回-1
        # - 其他情况返回NaN
        self.data['ob'] = pd.Series(np.select(
            [self.data['ob_bull'], self.data['ob_bear']],  # 条件列表
            [1, -1],                                       # 对应的返回值
            default=np.nan                                 # 默认值
        ), index=self.data.index)

        # 5. 使用前向填充处理NaN值
        # - 保持最后一个有效信号直到出现新的信号
        self.data['ob'] = self.data['ob'].ffill()
        self.data['mss'] = self.data['ob'].diff()
        self.data['mss'] = self.data['mss'].fillna(value=0)

        return self.data

    def mss_signal(self):
        self.detect_fractals()
        self.identify_fvg()
        self.find_orderblocks()
        return self.data.iloc[len(self.data)-1]['mss']

    def plot_signals(self):
        """可视化信号"""
        plt.figure(figsize=(16,  8))
        plt.plot(self.data['close'],  label='Price')
        plt.scatter(self.data.index,  self.data['fractal_highs'],  marker=2, c='g', label='Fractal High')
        plt.scatter(self.data.index,  self.data['fractal_lows'],  marker=3, c='r', label='Fractal Low')

        # # 绘制多头订单块信号
        # bullish_ob = self.data[self.data['ob_bull']]
        # plt.scatter(bullish_ob.index,  bullish_ob['close'], marker='^', c='orange', label='Bullish Order Block')
        #
        # # 绘制空头订单块信号
        # bearish_ob = self.data[self.data['ob_bear']]
        # plt.scatter(bearish_ob.index,  bearish_ob['close'], marker='v', c='lime', label='Bearish Order Block')

        bull_fvg = self.data[self.data['bull_fvg']]
        plt.scatter(bull_fvg.index,  bull_fvg['close'], marker=1, c='orangered', label='Bullish FVG')

        bear_fvg = self.data[self.data['bear_fvg']]
        plt.scatter(bear_fvg.index,  bear_fvg['close'], marker=0, c='limegreen', label='Bearish FVG')

        bull_mss = self.data[self.data['mss'] > 0]
        plt.scatter(bull_mss.index,  bull_mss['close'], marker="o", c='orangered', label='Bullish MSS')

        bear_mss = self.data[self.data['mss'] < 0]
        plt.scatter(bear_mss.index,  bear_mss['close'], marker="o", c='limegreen', label='Bearish MSS')

        plt.legend()
        plt.show()

    def plot_with_indicators(self):
        # 可视化配置
        # --------------------------------
        # 1. 分形标记
        ap1 = mpf.make_addplot(self.data['fractal_highs'],  type='scatter', markersize=80,
                               marker='v', color='firebrick', panel=0)
        ap2 = mpf.make_addplot(self.data['fractal_lows'],  type='scatter', markersize=80,
                               marker='^', color='forestgreen', panel=0)

        # 2. 订单块标记
        ob_bull = self.data['ob_bull'].replace(False,  np.nan) * self.data['high'] * 1.01
        ob_bear = self.data['ob_bear'].replace(False,  np.nan) * self.data['low'] * 0.99
        ap3 = mpf.make_addplot(ob_bull,  type='scatter', markersize=120,
                               marker='$\u2191$', color='royalblue', panel=0)
        ap4 = mpf.make_addplot(ob_bear,  type='scatter', markersize=120,
                               marker='$\u2193$', color='tomato', panel=0)

        # 3. 主图绘制
        fig, axes = mpf.plot(self.data,  type='candle', style='yahoo',
                             addplot=[ap1, ap2, ap3, ap4],
                             figsize=(16, 8), returnfig=True)

        # 4. FVG区域绘制（通过Matplotlib直接操作axes）
        ax = axes[0]
        for i in range(2, len(self.data)):
            # 牛市FVG区域（绿色半透明）
            if self.data['bull_fvg'].iloc[i]:
                x_start = self.data.index[i-2]
                x_end = self.data.index[i]
                y_top = self.data['high'].iloc[i-2]
                y_bottom = self.data['low'].iloc[i]
                ax.fill_betweenx([y_top,  y_bottom], x_start, x_end,
                                 color='limegreen', alpha=1)

            # 熊市FVG区域（红色半透明）
            if self.data['bear_fvg'].iloc[i]:
                x_start = self.data.index[i-2]
                x_end = self.data.index[i]
                y_bottom = self.data['low'].iloc[i-2]
                y_top = self.data['high'].iloc[i]
                ax.fill_betweenx([y_bottom,  y_top], x_start, x_end,
                                 color='orangered', alpha=1)

        plt.title('Fractal  Orderblocks with FVG Visualization')
        plt.show()


push_key = "5e926333c83d7bb5c9e65e2550ed9684e7d1357daa0f8d7cf865b57439c0aed0"


def bark_push(title, content):
    bark = BarkNotificator(device_token=push_key)
    bark.send(title=title, content=content,
              ringtone='minuet.caf', call='1')


def job(interval, symbol):
    time.sleep(7)
    df = get_futures_continous_data(symbol=symbol, interval=interval, limit=200)
    # df = get_data(symbol=symbol, interval=interval, limit=200)

    time_at = format_time(time.time())
    trend = "Long"
    ob = FractalOrderBlockStrategy(data=df)
    signal = ob.mss_signal()
    push = signal != 0
    print(f"{time_at} {df.tail(1).index[0]} {interval} {symbol} {signal}")
    if signal > 0:
        trend = "Long"
    if signal < 0:
        trend = "Short"

    if push:
        title = f"{symbol} {interval} mss detected"
        content = f"{time_at}\n\n{trend}\n\n"
        bark_push(title=title, content=content)


if __name__ == "__main__":
    job("15m", btc)
    job("3m", btc)

    def jobs(interval, symbol):
        ival = f"{interval}m"
        for i in range(60):
            if i % interval == 0:
                schedule.every().hour.at(f":{i:02d}").do(job, ival, symbol).tag(f"{ival}")

    def clear(interval):
        schedule.clear(f"{interval}m")

    jobs(15, btc)
    jobs(3, btc)

    while True:
        schedule.run_pending()
        time.sleep(1)


# pm2 start ob.py --time --interpreter /root/proj/backtester/.venv/bin/python3
